//
//  SVProgressHUD.swift
//  Valentine-Swift
//
//  Created by 陆文宇 on 2017/1/25.
//  Copyright © 2017年 陆文宇. All rights reserved.
//

import UIKit

class SVProgressHUD: UIView {
    
    enum MaskType: UInt {
        case none = 1      // default mask type, allow user interactions while HUD is displayed
        case clear = 2     // don't allow user interactions
        case black = 3     // don't allow user interactions and dim the UI in the back of the HUD, as on iOS 7 and above
        case gradient = 4  // don't allow user interactions and dim the UI with a a-la UIAlertView background gradient, as on iOS 6
        case custom = 5    // don't allow user interactions and dim the UI in the back of the HUD with a custom color
    }
    
    enum Style: Int {
        case light = 0        // default style, white HUD with black text, HUD background will be blurred on iOS 8 and above
        case dark = 1         // black HUD and white text, HUD background will be blurred on iOS 8 and above
        case darkTranslucent = 2
        case custom = 3       // uses the fore- and background color properties
    }
    
    enum AnimationType: UInt {
        case flat = 0     // default animation type, custom flat animation (indefinite animated ring)
        case native = 1   // iOS native UIActivityIndicatorView
    }
    
    static let SVProgressHUDDidAppearNotification: Notification.Name = Notification.Name("SVProgressHUDDidAppearNotification")
    static let SVProgressHUDWillAppearNotification: Notification.Name = Notification.Name("SVProgressHUDWillAppearNotification")
    static let SVProgressHUDDidReceiveTouchEventNotification: Notification.Name = Notification.Name("SVProgressHUDDidReceiveTouchEventNotification")
    static let SVProgressHUDDidTouchDownInsideNotification: Notification.Name = Notification.Name("SVProgressHUDDidTouchDownInsideNotification")
    static let SVProgressHUDStatusUserInfoKey = "SVProgressHUDStatusUserInfoKey"
    
    static let SVProgressHUDParallaxDepthPoints: CGFloat = 10
    static let SVProgressHUDUndefinedProgress: Float = -1
    static let SVProgressHUDDefaultAnimationDuration: TimeInterval = 0.15
    
    static let sharedView: SVProgressHUD = SVProgressHUD(frame: UIScreen.main.bounds)
    
    var defaultAnimationType: AnimationType = .flat
    var defaultStyle: Style = .darkTranslucent
    var defaultMaskType: MaskType = .clear
    var cornerRadius: CGFloat = 14
    var defaultTranslucentDegree: CGFloat = 0.7
    var defaultBackgroundColor: UIColor = UIColor.clear
    var defaultForegroundColor: UIColor = UIColor.black
    var backgroundLayerColor: UIColor = UIColor(white: 0, alpha: 0.4)
    var font: UIFont
    var backgroundLayer: CALayer?
    var offsetFromCenter: UIOffset = UIOffset.zero
    var minimumSize: CGSize = CGSize.zero
    var minimumDissTimeInterval: TimeInterval = 2.0
    
    var backgroundColorForStyle: UIColor {
        switch defaultStyle {
        case .light:
            return .white
        case .dark:
            return .black
        case .darkTranslucent:
            return UIColor.black.withAlphaComponent(defaultTranslucentDegree)
        default:
            return defaultBackgroundColor
        }
    }
    
    var foregroundColorForStyle: UIColor {
        switch defaultStyle {
        case .light:
            return .black
        case .dark:
            return .white
        case .darkTranslucent:
            return .white
        default:
            return defaultForegroundColor
        }
    }
    
    var fadeOutTimer: Timer?
    var infoImage: UIImage
    var errorImage: UIImage
    var successImage: UIImage
    var ringThickness: CGFloat = 2
    var ringRadius: CGFloat = 18
    var ringNoTextRadius: CGFloat = 24
    var fadeInAnimationDuration: TimeInterval = SVProgressHUD.SVProgressHUDDefaultAnimationDuration
    var fadeOutAnimationDuration: TimeInterval = SVProgressHUD.SVProgressHUDDefaultAnimationDuration
    var activityCount: UInt = 0
    var progress: Float = 0
    lazy var imageView: UIImageView = {
        let _imageView: UIImageView = UIImageView(frame: CGRect(x: 0, y: 0, width: 28, height: 28))
        return _imageView
    }()
    lazy var _statusLabel: UILabel = {
        let statusLabel: UILabel = UILabel(frame: CGRect.zero)
        statusLabel.backgroundColor = UIColor.clear
        statusLabel.adjustsFontSizeToFitWidth = true
        statusLabel.textAlignment = .center
        statusLabel.baselineAdjustment = .alignCenters
        statusLabel.numberOfLines = 0
        return statusLabel
    }()
    var statusLabel: UILabel {
        set {
            _statusLabel = newValue
        }
        
        get {
            _statusLabel.textColor = foregroundColorForStyle
            _statusLabel.font = font
            return _statusLabel
        }
    }
    var _hudView: UIView = {
        let hudView: UIView = UIView(frame: CGRect.zero)
        hudView.layer.masksToBounds = true
        hudView.autoresizingMask = [.flexibleBottomMargin, .flexibleTopMargin, .flexibleRightMargin, .flexibleLeftMargin]
        return hudView
    }()
    var hudView: UIView {
        set {
            _hudView = newValue
        }
        
        get {
            _hudView.layer.cornerRadius = cornerRadius
            _hudView.backgroundColor = backgroundColorForStyle
            return _hudView
        }
    }
    lazy var overlayView: UIControl = {
        let _overlayView: UIControl = UIControl(frame: UIScreen.main.bounds)
        _overlayView.autoresizingMask = [.flexibleWidth, .flexibleHeight]
        _overlayView.backgroundColor = UIColor.clear
        return _overlayView
    }()
    var notificationUserInfo: Dictionary<String, String>? {
        return statusLabel.text != nil ? [SVProgressHUD.SVProgressHUDStatusUserInfoKey: statusLabel.text!] : nil
    }
    
    var _indefiniteAnimatedView: UIView?
    var indefiniteAnimatedView: UIView! {
        set {
            _indefiniteAnimatedView = newValue
        }
        
        get {
            if defaultAnimationType == .flat {
                if _indefiniteAnimatedView != nil && !(_indefiniteAnimatedView is SVIndefiniteAnimatedView) {
                    _indefiniteAnimatedView?.removeFromSuperview()
                    _indefiniteAnimatedView = nil
                }
                
                if _indefiniteAnimatedView == nil {
                    _indefiniteAnimatedView = SVIndefiniteAnimatedView(frame: CGRect.zero, radius: statusLabel.text != nil ? ringRadius : ringNoTextRadius, strokeThickness: ringThickness, strokeColor: foregroundColorForStyle)
                } else {
                    let temp: SVIndefiniteAnimatedView = _indefiniteAnimatedView as! SVIndefiniteAnimatedView
                    temp.strokeColor = foregroundColorForStyle
                    temp.strokeThickness = ringThickness
                    temp.radius = statusLabel.text != nil ? ringRadius : ringNoTextRadius
                    temp.reInit()
                }
            } else {
                if _indefiniteAnimatedView != nil && !(_indefiniteAnimatedView is UIActivityIndicatorView) {
                    _indefiniteAnimatedView?.removeFromSuperview()
                    _indefiniteAnimatedView = nil
                }
                
                if _indefiniteAnimatedView == nil {
                    _indefiniteAnimatedView = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge)
                }
                
                let temp: UIActivityIndicatorView = _indefiniteAnimatedView as! UIActivityIndicatorView
                temp.color = foregroundColorForStyle
            }
            _indefiniteAnimatedView?.sizeToFit()
            
            return _indefiniteAnimatedView!
        }
    }
    
    var _ringView: SVProgressAnimatedView = SVProgressAnimatedView(frame: CGRect.zero)
    var ringView: SVProgressAnimatedView {
        set {
            _ringView = newValue
        }
        
        get {
            _ringView.strokeColor = foregroundColorForStyle
            _ringView.strokeThickness = ringThickness
            _ringView.radius = statusLabel.text != nil ? ringRadius : ringNoTextRadius
            _ringView.reInit()
            return _ringView
        }
    }
    
    var _backgroundRingView: SVProgressAnimatedView = {
        let backgroundRingView: SVProgressAnimatedView = SVProgressAnimatedView(frame: CGRect.zero)
        backgroundRingView.strokeEnd = 1
        return backgroundRingView
    }()
    var backgroundRingView: SVProgressAnimatedView {
        set {
            _backgroundRingView = newValue
        }
        
        get {
            _backgroundRingView.strokeColor = foregroundColorForStyle.withAlphaComponent(0.1)
            _backgroundRingView.strokeThickness = ringThickness
            _backgroundRingView.radius = statusLabel.text != nil ? ringRadius : ringNoTextRadius
            return _backgroundRingView
        }
    }
    
    var visibleKeyboardHeight: CGFloat {
        var keyboardWindow: UIWindow? = nil
        for testWindiw in UIApplication.shared.windows {
            if !(testWindiw.isKind(of: NSClassFromString("UIWindow")!)) {
                keyboardWindow = testWindiw
                break
            }
        }
        
        if let keyboardWindow = keyboardWindow {
            for possibleKeyboard in keyboardWindow.subviews {
                if possibleKeyboard.isKind(of: NSClassFromString("UIPeripheralHostView")!) || possibleKeyboard.isKind(of: NSClassFromString("UIKeyboard")!) {
                    return possibleKeyboard.bounds.height
                } else if possibleKeyboard.isKind(of: NSClassFromString("UIInputSetContainerView")!) {
                    for possibleKeyboardSubview in possibleKeyboard.subviews {
                        if possibleKeyboardSubview.isKind(of: NSClassFromString("UIInputSetHostView")!) {
                            return possibleKeyboardSubview.bounds.height
                        }
                    }
                }
            }
        }
        return 0
    }
    
    override init(frame: CGRect) {
        if UIFont.responds(to: #selector(UIFont.preferredFont(forTextStyle:))) {
            font = UIFont.preferredFont(forTextStyle: .subheadline)
        } else {
            font = UIFont.systemFont(ofSize: 14)
        }
        
        let bundle: Bundle = Bundle(for: SVProgressHUD.self)
        let url: URL = bundle.url(forResource: "SVProgressHUD", withExtension: "bundle")!
        let imageBundle: Bundle = Bundle(url: url)!
        
        let infoImage: UIImage = UIImage(contentsOfFile: imageBundle.path(forResource: "info", ofType: "png")!)!
        let successImage: UIImage = UIImage(contentsOfFile: imageBundle.path(forResource: "success", ofType: "png")!)!
        let errorImage: UIImage = UIImage(contentsOfFile: imageBundle.path(forResource: "error", ofType: "png")!)!
        
        if UIImage.instancesRespond(to: #selector(UIImage.withRenderingMode(_:))) {
            self.infoImage = infoImage.withRenderingMode(.alwaysTemplate)
            self.successImage = successImage.withRenderingMode(.alwaysTemplate)
            self.errorImage = errorImage.withRenderingMode(.alwaysTemplate)
        } else {
            self.infoImage = infoImage
            self.successImage = successImage
            self.errorImage = errorImage
        }
        
        super.init(frame: frame)
        
        overlayView.addTarget(self, action: #selector(overlayViewDidReceiveTouchEvent(sender:for:)), for: .touchDown)
        
        isUserInteractionEnabled = false
        alpha = 0
        
        accessibilityIdentifier = "SVProgressHUD"
        accessibilityLabel = "SVProgressHUD"
        isAccessibilityElement = true
    }
    
    required convenience init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }
    
    @objc func overlayViewDidReceiveTouchEvent(sender: AnyObject, for event: UIEvent) -> Void {
        NotificationCenter.default.post(name: SVProgressHUD.SVProgressHUDDidReceiveTouchEventNotification, object: self, userInfo: notificationUserInfo)
        
        let touch: UITouch = event.allTouches!.first!
        let touchLocation: CGPoint = touch.location(in: self)
        
        if hudView.frame.contains(touchLocation) {
            NotificationCenter.default.post(name: SVProgressHUD.SVProgressHUDDidTouchDownInsideNotification, object: self, userInfo: notificationUserInfo)
        }
    }
    
    func updateViewHierachy() -> Void {
        if overlayView.superview == nil {
            for window in UIApplication.shared.windows.reversed() {
                let windowOnMainScreen: Bool = window.screen == UIScreen.main
                let windowIsVisible: Bool = !window.isHidden && window.alpha > 0
                let windowLevelNormal: Bool = window.windowLevel == UIWindowLevelNormal
                
                if windowOnMainScreen && windowIsVisible && windowLevelNormal {
                    window.addSubview(overlayView)
                    break
                }
            }
        } else {
            overlayView.superview?.bringSubview(toFront: overlayView)
        }
        
        if superview == nil {
            overlayView.addSubview(self)
        }
        if hudView.superview == nil {
            addSubview(hudView)
        }
        if imageView.superview == nil {
            hudView.addSubview(imageView)
        }
        if statusLabel.superview == nil {
            hudView.addSubview(statusLabel)
        }
    }
    
    func cancelIndefiniteAnimatedViewAnimation() -> Void {
        if indefiniteAnimatedView.responds(to: #selector(UIActivityIndicatorView.stopAnimating)) {
            indefiniteAnimatedView.perform(#selector(UIActivityIndicatorView.stopAnimating))
        }
        
        indefiniteAnimatedView.removeFromSuperview()
    }
    
    func cancelRingLayerAnimation() -> Void {
        CATransaction.begin()
        CATransaction.setDisableActions(true)
        
        hudView.layer.removeAllAnimations()
        ringView.strokeEnd = 0
        
        CATransaction.commit()
        
        ringView.removeFromSuperview()
        backgroundRingView.removeFromSuperview()
    }
    
    func updateMask() -> Void {
        if backgroundLayer != nil {
            backgroundLayer?.removeFromSuperlayer()
            backgroundLayer = nil
        }
        switch defaultMaskType {
        case .custom, .black:
            backgroundLayer = CALayer()
            backgroundLayer?.frame = bounds
            backgroundLayer?.backgroundColor = defaultMaskType == SVProgressHUD.MaskType.custom ? backgroundLayerColor.cgColor : UIColor(white: 0, alpha: 0.4).cgColor
            backgroundLayer?.setNeedsDisplay()
            
            layer.insertSublayer(backgroundLayer!, at: 0)
        case .gradient:
            let layer: SVRadialGradientLayer = SVRadialGradientLayer()
            backgroundLayer = layer
            backgroundLayer?.frame = bounds
            var gradientCenter: CGPoint = center
            gradientCenter.y = (bounds.size.height - visibleKeyboardHeight) / 2
            layer.gradientCenter = gradientCenter
            backgroundLayer?.setNeedsDisplay()
            
            self.layer.insertSublayer(backgroundLayer!, at: 0)
        default:
            break
        }
    }
    
    func updateMotionEffect(typeX: UIInterpolatingMotionEffectType, typeY: UIInterpolatingMotionEffectType) -> Void {
        if hudView.responds(to: #selector(UIView.addMotionEffect(_:))) {
            let effectX: UIInterpolatingMotionEffect = UIInterpolatingMotionEffect(keyPath: "center.x", type: typeX)
            effectX.minimumRelativeValue = -SVProgressHUD.SVProgressHUDParallaxDepthPoints
            effectX.maximumRelativeValue = SVProgressHUD.SVProgressHUDParallaxDepthPoints
            
            let effectY: UIInterpolatingMotionEffect = UIInterpolatingMotionEffect(keyPath: "center.y", type: typeY)
            effectY.minimumRelativeValue = -SVProgressHUD.SVProgressHUDParallaxDepthPoints
            effectY.maximumRelativeValue = SVProgressHUD.SVProgressHUDParallaxDepthPoints
            
            let effectGroup: UIMotionEffectGroup = UIMotionEffectGroup()
            effectGroup.motionEffects = [effectX, effectY]
            
            hudView.motionEffects = []
            hudView.addMotionEffect(effectGroup)
        }
    }
    
    func updateMotionEffect(for orientation: UIInterfaceOrientation) -> Void {
        let xMotionEffectType: UIInterpolatingMotionEffectType = UIInterfaceOrientationIsPortrait(orientation) ? UIInterpolatingMotionEffectType.tiltAlongHorizontalAxis : UIInterpolatingMotionEffectType.tiltAlongVerticalAxis
        let yMotionEffectType: UIInterpolatingMotionEffectType = UIInterfaceOrientationIsPortrait(orientation) ? UIInterpolatingMotionEffectType.tiltAlongVerticalAxis : UIInterpolatingMotionEffectType.tiltAlongHorizontalAxis
        updateMotionEffect(typeX: xMotionEffectType, typeY: yMotionEffectType)
    }
    
    @objc func positionHUD(notification: Notification?) -> Void {
        var keyboardHeight: CGFloat = 0
        var animationDuration: TimeInterval = 0
        
        frame = UIApplication.shared.delegate!.window!!.bounds
        let orientation: UIInterfaceOrientation = UIApplication.shared.statusBarOrientation
        
        var ignoreOrientation: Bool = false
        if ProcessInfo.processInfo.responds(to: #selector(getter: ProcessInfo.operatingSystemVersion)) {
            ignoreOrientation = true
        }
        
        if let notification = notification {
            if let keyboardInfo: Dictionary<String, Any> = notification.userInfo as? Dictionary<String, Any> {
                let keyboardFrame: CGRect = keyboardInfo[UIKeyboardFrameBeginUserInfoKey] as! CGRect
                animationDuration = keyboardInfo[UIKeyboardAnimationDurationUserInfoKey] as! TimeInterval
                
                if notification.name == NSNotification.Name.UIKeyboardWillShow || notification.name == NSNotification.Name.UIKeyboardDidShow {
                    keyboardHeight = keyboardFrame.width
                    
                    if ignoreOrientation || UIInterfaceOrientationIsPortrait(orientation) {
                        keyboardHeight = keyboardFrame.height
                    }
                }
            }
        } else {
            keyboardHeight = visibleKeyboardHeight
        }
        
        var orientationFrame: CGRect = bounds
        
        var statusBarFrame: CGRect = UIApplication.shared.statusBarFrame
        
        if !ignoreOrientation && UIInterfaceOrientationIsLandscape(orientation) {
            var temp: CGFloat = orientationFrame.width
            orientationFrame.size.width = orientationFrame.height
            orientationFrame.size.height = temp
            
            temp = statusBarFrame.height
            statusBarFrame.size.width = statusBarFrame.height
            statusBarFrame.size.height = temp
        }
        
        updateMotionEffect(for: orientation)
        
        var activeHeight: CGFloat = orientationFrame.height
        if keyboardHeight > 0 {
            activeHeight += statusBarFrame.height * 2
        }
        activeHeight -= keyboardHeight
        
        let posX: CGFloat = orientationFrame.width / 2
        let posY: CGFloat = CGFloat(floorf(Float(activeHeight) * 0.45))
        
        var rotateAngle: CGFloat = 0
        var newCenter: CGPoint = CGPoint(x: posX, y: posY)
        
        if !ignoreOrientation {
            switch orientation {
            case .portraitUpsideDown:
                rotateAngle = CGFloat.pi
                newCenter = CGPoint(x: posX, y: orientationFrame.height - posY)
            case .landscapeLeft:
                rotateAngle = -CGFloat.pi / 2
                newCenter = CGPoint(x: posY, y: posX)
            case .landscapeRight:
                rotateAngle = CGFloat.pi / 2
                newCenter = CGPoint(x: orientationFrame.height - posY, y: posX)
            default:
                rotateAngle = 0
                newCenter = CGPoint(x: posX, y: posY)
            }
        }
        
        if notification != nil {
            UIView.animate(withDuration: animationDuration, delay: 0, options: [.allowUserInteraction], animations: {
                self.move(to: newCenter, rotateAngle: rotateAngle)
                self.hudView.setNeedsDisplay()
            }, completion: nil)
        } else {
            move(to: newCenter, rotateAngle: rotateAngle)
            hudView.setNeedsDisplay()
        }
        
        updateMask()
    }
    
    func move(to newCenter: CGPoint, rotateAngle angle: CGFloat)  -> Void {
        hudView.transform = CGAffineTransform(rotationAngle: angle)
        hudView.center = CGPoint(x: newCenter.x + offsetFromCenter.horizontal, y: newCenter.y + offsetFromCenter.vertical)
    }
    
    func updateHUDFrame() ->  Void {
        var hudWidth: CGFloat = 100
        var hudHeight: CGFloat = 100
        let stringHeightBuffer: CGFloat = 20
        let stringAndContentHeightBuffer: CGFloat = 80
        var labelRect: CGRect = CGRect.zero
        
        let imageUsed: Bool = imageView.image != nil && !imageView.isHidden
        let progressUsed: Bool = imageView.isHidden
        
        let string: NSString? = statusLabel.text as NSString?
        if let string = string {
            let constraintSize: CGSize = CGSize(width: 200, height: 300)
            let stringRect: CGRect
            if string.responds(to: #selector(NSString.boundingRect(with:options:attributes:context:))) {
                stringRect = string.boundingRect(with: constraintSize, options: [.usesFontLeading, .truncatesLastVisibleLine, .usesLineFragmentOrigin], attributes: [NSAttributedStringKey.font: statusLabel.font], context: nil)
            } else {
                var stringSize: CGSize = .zero
                if string.responds(to: #selector(NSString.size(withAttributes:))) {
                    stringSize = string.size(withAttributes: [NSAttributedStringKey.font: UIFont(name: statusLabel.font.fontName, size: statusLabel.font.pointSize)!])
                }
                stringRect = CGRect(x: 0, y: 0, width: stringSize.width, height: stringSize.height)
            }
            
            let stringWidth: CGFloat = stringRect.size.width
            let stringHeight: CGFloat = CGFloat(ceilf(Float(stringRect.height)))
            
            if imageUsed || progressUsed {
                hudHeight = stringAndContentHeightBuffer + stringHeight
            } else {
                hudHeight = stringHeightBuffer + stringHeight
            }
            if stringWidth > hudWidth {
                hudWidth = CGFloat(ceilf(Float(stringWidth) / 2) * 2)
            }
            let labelRectY: CGFloat = imageUsed || progressUsed ? 68 : 9
            if hudHeight > 100 {
                labelRect = CGRect(x: 12, y: labelRectY, width: hudWidth, height: stringHeight)
                hudWidth += 24
            } else {
                hudWidth += 24
                labelRect = CGRect(x: 0, y: labelRectY, width: hudWidth, height: stringHeight)
            }
        }
        
        hudView.bounds = CGRect(x: 0, y: 0, width: max(minimumSize.width, hudWidth), height: max(minimumSize.height, hudHeight))
        labelRect.size.width += max(0, minimumSize.width - hudWidth)
        updateBlurBounds()
        
        if string != nil {
            imageView.center = CGPoint(x: hudView.bounds.width / 2, y: 36)
        } else {
            imageView.center = CGPoint(x: hudView.bounds.width / 2, y: hudView.bounds.height / 2)
        }
        
        statusLabel.isHidden = false
        statusLabel.frame = labelRect
        
        CATransaction.begin()
        CATransaction.setDisableActions(true)
        
        if string != nil {
            if progress != SVProgressHUD.SVProgressHUDUndefinedProgress {
                ringView.center = CGPoint(x: hudView.bounds.width / 2, y: 36)
                backgroundRingView.center = ringView.center
            } else {
                if defaultAnimationType == .flat {
                    let indefiniteAnimationView: SVIndefiniteAnimatedView = indefiniteAnimatedView as! SVIndefiniteAnimatedView
                    indefiniteAnimationView.radius = ringRadius
                    indefiniteAnimationView.sizeToFit()
                }
                
                let center: CGPoint = CGPoint(x: hudView.bounds.width / 2, y: 36)
                indefiniteAnimatedView.center = center
            }
        } else {
            if progress != SVProgressHUD.SVProgressHUDUndefinedProgress {
                ringView.center = CGPoint(x: hudView.bounds.width / 2, y: hudView.bounds.height / 2)
                backgroundRingView.center = ringView.center
            } else {
                if defaultAnimationType == .flat {
                    let indefiniteAnimationView: SVIndefiniteAnimatedView = indefiniteAnimatedView as! SVIndefiniteAnimatedView
                    indefiniteAnimationView.radius = ringNoTextRadius
                    indefiniteAnimationView.sizeToFit()
                }
                
                let center: CGPoint = CGPoint(x: hudView.bounds.width / 2, y: hudView.bounds.height / 2)
                indefiniteAnimatedView.center = center
            }
        }
        
        CATransaction.commit()
    }
    
    func updateBlurBounds() -> Void {
        if NSClassFromString("UIBlurEffect") != nil && defaultStyle != .custom {
            hudView.backgroundColor = UIColor.clear
            
            for subview in hudView.subviews {
                if subview is UIVisualEffectView {
                    subview.removeFromSuperview()
                }
            }
            
            if defaultBackgroundColor != UIColor.clear {
                let blurEffectStyle: UIBlurEffectStyle = defaultStyle == .dark ? .dark : .light
                let blurEffect: UIBlurEffect = UIBlurEffect(style: blurEffectStyle)
                let blurEffectView: UIVisualEffectView = UIVisualEffectView(effect: blurEffect)
                blurEffectView.autoresizingMask = hudView.autoresizingMask
                blurEffectView.frame = hudView.bounds
                
                let vibrancyEffect: UIVisualEffect = UIVibrancyEffect(blurEffect: blurEffect)
                let vibrancyEffectView: UIVisualEffectView = UIVisualEffectView(effect: vibrancyEffect)
                vibrancyEffectView.autoresizingMask = blurEffectView.autoresizingMask
                vibrancyEffectView.bounds = blurEffectView.bounds
                blurEffectView.contentView.addSubview(vibrancyEffectView)
                
                hudView.insertSubview(blurEffectView, at: 0)
            }
        }
    }
    
    func showStatus(_ status: String?) -> Void {
        updateHUDFrame()
        positionHUD(notification: nil)
        
        if defaultMaskType == .none {
            overlayView.isUserInteractionEnabled = true
            accessibilityLabel = status
            isAccessibilityElement = true
        } else {
            overlayView.isUserInteractionEnabled = false
            hudView.accessibilityLabel = status
            hudView.isAccessibilityElement = true
        }
        
        overlayView.backgroundColor = UIColor.clear
        
        if alpha != 1 || hudView.alpha != 1 {
            NotificationCenter.default.post(name: SVProgressHUD.SVProgressHUDWillAppearNotification, object: self, userInfo: notificationUserInfo)
            
            hudView.transform.scaledBy(x: 1.3, y: 1.3)
            
            alpha = 0
            hudView.alpha = 0
            
            let animationsClosure: () -> Void = {
                self.hudView.transform.scaledBy(x: 1 / 1.3, y: 1 / 1.3)
                self.alpha = 1
                self.hudView.alpha = 1
            }
            
            let completionClosure: () -> Void = {
                self.registerNotifications()
                
                NotificationCenter.default.post(name: SVProgressHUD.SVProgressHUDDidAppearNotification, object: self, userInfo: self.notificationUserInfo)
                
                UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil)
                UIAccessibilityPostNotification(UIAccessibilityAnnouncementNotification, status)
            }
            
            if fadeInAnimationDuration > 0 {
                UIView.animate(withDuration: fadeInAnimationDuration, delay: 0, options: [.allowUserInteraction, .curveEaseOut, .beginFromCurrentState], animations: {
                    animationsClosure()
                }, completion: { (finished) in
                    completionClosure()
                })
            } else {
                animationsClosure()
                completionClosure()
            }
            
            setNeedsDisplay()
        }
    }
    
    func registerNotifications() -> Void {
        NotificationCenter.default.addObserver(self, selector: #selector(SVProgressHUD.positionHUD(notification:)), name: Notification.Name.UIApplicationDidChangeStatusBarOrientation, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(SVProgressHUD.positionHUD(notification:)), name: Notification.Name.UIKeyboardWillHide, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(SVProgressHUD.positionHUD(notification:)), name: Notification.Name.UIKeyboardDidHide, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(SVProgressHUD.positionHUD(notification:)), name: Notification.Name.UIKeyboardWillShow, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(SVProgressHUD.positionHUD(notification:)), name: Notification.Name.UIKeyboardDidShow, object: nil)
        NotificationCenter.default.addObserver(self, selector: #selector(SVProgressHUD.positionHUD(notification:)), name: Notification.Name.UIApplicationDidBecomeActive, object: nil)
    }
    
    func showProgress(_ progress: Float, with status: String?) -> Void {
        OperationQueue.main.addOperation {
            self.updateViewHierachy()
            
            self.imageView.isHidden = true
            self.imageView.image = nil
            
            if self.fadeOutTimer != nil {
                self.activityCount = 0
            }
            self.fadeOutTimer?.invalidate()
            
            self.statusLabel.text = status
            self.progress = progress
            
            if progress >= 0 {
                self.cancelIndefiniteAnimatedViewAnimation()
                self.hudView.addSubview(self.ringView)
                self.hudView.addSubview(self.backgroundRingView)
                self.ringView.strokeEnd = progress
                
                if progress == 0 {
                    self.activityCount += 1
                }
            } else  {
                self.cancelRingLayerAnimation()
                
                self.indefiniteAnimatedView.removeFromSuperview()
                self.indefiniteAnimatedView = nil
                self.hudView.addSubview(self.indefiniteAnimatedView)
                if self.indefiniteAnimatedView.responds(to: #selector(UIActivityIndicatorView.startAnimating)) {
                    self.indefiniteAnimatedView.perform(#selector(UIActivityIndicatorView.startAnimating))
                }
                
                self.activityCount += 1
            }
            
            self.showStatus(status)
        }
    }
    
    func show(image: UIImage, status: String?, duration: TimeInterval) -> Void {
        OperationQueue.main.addOperation {
            self.fadeOutTimer?.invalidate()
            self.updateViewHierachy()
            
            self.progress = SVProgressHUD.SVProgressHUDUndefinedProgress
            self.cancelRingLayerAnimation()
            self.cancelIndefiniteAnimatedViewAnimation()
            
            let tintColor: UIColor = self.foregroundColorForStyle
            var tintedImage: UIImage? = image
            if self.imageView.responds(to: #selector(setter: UIImageView.tintColor)) {
                if tintedImage?.renderingMode != .alwaysTemplate {
                    tintedImage = image.withRenderingMode(.alwaysTemplate)
                }
                self.imageView.tintColor = tintColor
            } else {
                tintedImage = self.tintedImage(image, with: tintColor)
            }
            self.imageView.image = tintedImage
            self.imageView.isHidden = false
            
            self.statusLabel.text = status
            
            self.showStatus(status)
            
            self.fadeOutTimer = Timer(timeInterval: duration, target: self, selector: #selector(SVProgressHUD.dismissTimer(sender:)), userInfo: nil, repeats: false)
            RunLoop.main.add(self.fadeOutTimer!, forMode: .commonModes)
        }
    }
    
    @objc func dismissTimer(sender: Timer) -> Void {
        SVProgressHUD.sharedView.dismiss()
    }
    
    func tintedImage(_ image: UIImage, with tintColor: UIColor) -> UIImage? {
        let rect: CGRect = CGRect(x: 0, y: 0, width: image.size.width, height: image.size.height)
        UIGraphicsBeginImageContextWithOptions(rect.size, false, image.size.height)
        let c: CGContext? = UIGraphicsGetCurrentContext()
        image.draw(in: rect)
        c?.setFillColor(tintColor.cgColor)
        c?.setBlendMode(.sourceAtop)
        c?.fill(rect)
        let tintedImage: UIImage? = UIGraphicsGetImageFromCurrentImageContext()
        UIGraphicsEndImageContext()
        
        return tintedImage
    }
    
    func dismiss(with delay: TimeInterval = 0) -> Void {
        OperationQueue.main.addOperation {
            if self.alpha != 0 || self.hudView.alpha != 0 {
                NotificationCenter.default.post(name: SVProgressHUD.SVProgressHUDWillAppearNotification, object: nil, userInfo: self.notificationUserInfo)
                
                self.activityCount = 0
                
                let animationsClosure: () -> Void = {
                    self.hudView.transform.scaledBy(x: 0.8, y: 0.8)
                    self.alpha = 0
                    self.hudView.alpha = 0
                }
                
                let completionClosure: () -> Void = {
                    if self.alpha == 0 {
                        self.overlayView.removeFromSuperview()
                        self.hudView.removeFromSuperview()
                        self.removeFromSuperview()
                        
                        self.progress = SVProgressHUD.SVProgressHUDUndefinedProgress
                        self.cancelRingLayerAnimation()
                        self.cancelIndefiniteAnimatedViewAnimation()
                        
                        NotificationCenter.default.removeObserver(self)
                        
                        NotificationCenter.default.post(name: SVProgressHUD.SVProgressHUDDidAppearNotification, object: self, userInfo: self.notificationUserInfo)
                        
                        let rootController: UIViewController = UIApplication.shared.keyWindow!.rootViewController!
                        if rootController.responds(to: #selector(UIViewController.setNeedsStatusBarAppearanceUpdate)) {
                            rootController.setNeedsStatusBarAppearanceUpdate()
                        }
                        UIAccessibilityPostNotification(UIAccessibilityScreenChangedNotification, nil)
                    }
                }
                
                if self.fadeOutAnimationDuration > 0 {
                    UIView.animate(withDuration: self.fadeOutAnimationDuration, delay: delay, options: [.allowUserInteraction, .curveEaseIn, .beginFromCurrentState], animations: {
                        animationsClosure()
                    }, completion: { (finished) in
                        completionClosure()
                    })
                } else {
                    animationsClosure()
                    completionClosure()
                }
                
                self.setNeedsDisplay()
            }
        }
    }
    
    static func displayDuration(for string: String) -> TimeInterval {
        return max(Double(string.count) * 0.06 + 0.5, sharedView.minimumDissTimeInterval)
    }
    
    static func show(image: UIImage, status: String?) -> Void {
        let displayInterval: TimeInterval = displayDuration(for: status ?? "")
        sharedView.show(image: image, status: status, duration: displayInterval)
    }
    
    static func showInfo(with status: String) -> Void {
        show(image: sharedView.infoImage, status: status)
    }
    
    static func showSuccess(with status: String?) -> Void {
        show(image: sharedView.successImage, status: status)
    }
    
    static func showError(with status: String?) -> Void {
        show(image: sharedView.errorImage, status: status)
    }
    
    static func showStatus(_ status: String?) -> Void {
        let _ = sharedView
        showProgress(SVProgressHUDUndefinedProgress, with: status)
    }
    
    static func show() -> Void {
        let _ = sharedView
        showProgress(SVProgressHUDUndefinedProgress, with: nil)
    }
    
    static func showProgress(_ progress: Float, with status: String?) -> Void {
        sharedView.showProgress(progress, with: status)
    }
    
    static func dismiss(with delay: TimeInterval = 0) -> Void {
        sharedView.dismiss(with: delay)
    }
    
}
